/*
 *
 *  Copyright (C) 2010 Tal Eisenberg
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package tal.drivechain.chain.shell;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.TreeMap;
import tal.drivechain.chain.Chain;
import tal.drivechain.chain.ChainNode;
import tal.drivechain.chain.FreePatcher;
import tal.drivechain.chain.NodeExport;
import tal.drivechain.chain.NodeInfo;
import tal.drivechain.chain.ParallelPatcher;
import tal.drivechain.chain.Patcher;
import tal.drivechain.chain.SerialPatcher;
import tal.drivechain.module.properties.KeyValueParser;
import tal.drivechain.module.properties.PropertyParser;
import tal.drivechain.module.xmlconfig.ModuleInfo;
import tal.drivechain.module.xmlconfig.PropertyInfo;
import tal.drivechain.parsing.Tokenizer;
import tal.drivechain.plugin.Module;
import tal.drivechain.system.Globals;
import tal.drivechain.system.PluginLoader;
import tal.drivechain.variable.VarTable;
import tal.drivechain.variable.Variable;
import tal.drivechain.variable.types.DataType;

/**
 *
 * @author eisental
 */
public class InfoPrinter {

    public static String printNode(ChainNode n) {
        boolean isChain = (n instanceof Chain);
        String ret = "";
        ret += (isChain?"Chain ": "Module ") + n.getName() + (!isChain?" (" + n.getNodeClassName() + ")":"") + " - " + (n.isRunning()?"(open)":"(closed)") + ":\n";

        if (isChain) {
            // patcher
            Chain c = (Chain)n;
            if (c.getPatcher()!=null) {
                Patcher p = c.getPatcher();
                if (p instanceof FreePatcher)
                    ret += (" - Patcher: free\n");
                else if (p instanceof SerialPatcher)
                    ret += (" - Patcher: serial\n");
                else if (p instanceof ParallelPatcher)
                    ret += (" - Patcher: parallel\n");
                else ret += (" - Patcher: " + p.getClass().getName());
            } else ret += " - Patcher: none\n";

            if (c.getChildNodes().size()>0) {
                ret += " - Child nodes:\n";
                ret += "      ";
                for (ChainNode cn : c.getChildNodes().values())
                    ret += cn.getName() + " (" + cn.getNodeClassName() + "), ";
                ret = ret.substring(0, ret.length()-2) + "\n";
            } else ret += " - Contains no child nodes.\n";
        }

        ret += printConnections(n);

        if (n.getNodeInfo().getProperties().size()>0) {
            ret += (" - Properties:\n");
            for (PropertyInfo prop : n.getNodeInfo().getProperties()) {
                if (n.isPropertyGettable(prop.getName())) {
                    Object value = n.getProperty(prop.getName());
                    String props = printProperty(n, prop, value);
                    String[] split = new Tokenizer(props, ';').getTokens();
                    for (String p : split) {
                        ret += "      * " + p + "\n";
                    }

                } else ret += "      * " + printProperty(n, prop, null) + "\n";
            }
        }

        if (isChain) {
            Chain c = (Chain)n;

            if (c.getScriptFilePath()!=null) {
                ret += " - Script file path: " + c.getScriptFilePath() + "\n";
            } else ret += " - Chain does not have a script file path.\n";
        }

        ret += " - Log flags: debug is " + (n.getDebugFlag()?"on":"off") + ", info is " + (n.getInfoFlag()?"on":"off") + "\n";

        return ret;
    }

    private static String printChildMap(Chain c, int tab, String prefix, Chain activeChain, boolean includeNodes) {
        String ret = "";
        String stab = "";
        for (int i=0; i<tab*3; i++)
            stab += " ";

        for (ChainNode child : c.getChildNodes().values()) {
            if (child instanceof Chain) {
                ret += stab + printNodeLine(prefix, child, activeChain);
                ret += printChildMap((Chain)child, tab+1, prefix + child.getName() + ".", activeChain, includeNodes);
                if (includeNodes && ((Chain)child).getChildNodes().size()>1) ret += stab + ".. (back to " + c.getName() + ")\n";
            } else if (includeNodes)
                ret += stab + printNodeLine(prefix, child, activeChain);
        }

        return ret;
    }

    private static String printNodeLine(String prefix, ChainNode c, Chain activeChain) {
        String disp = "";
        if (c instanceof Chain) { // chain
            if (activeChain!=null && c==activeChain)
                disp = "-> ";
            else disp = " + ";
            disp += c.getName();
        } else { // module
            disp = " . " + c.getName() + " - " + c.getNodeClassName();
        }

        return disp + "\n";
    }

    public static String printMap(Chain rootChain, Chain activeChain, boolean includeNodes) {
        String ret = "";
        if (Globals.getRootChain()==null)
            return "There are currently no chains to display";
        else {
            ret = printNodeLine("", rootChain, activeChain);
            ret += printChildMap(rootChain, 1, rootChain.getName() + ".", activeChain, includeNodes);
            return ret;
        }
    }

    public static String printNodeExports(Chain c) {
        String ret  = "";
        if (c.getNodeExports().size()>0) {
            ret += "   Exports:\n";
            for (ChainNode n : c.getNodeExports().keySet()) ret += printNodeExport(c, n);
            
        } else ret += "   No exported properties.\n";
        return ret;
    }

    public static String printNodeExport(Chain c, ChainNode n) {
        String ret = "";
        NodeExport e = c.getNodeExports().get(n);
        if (e==null) return "Node " + n.getName() + " in chain " + c.getPath() + " has no exports.";
        ret += "      " + e.getExportedNode().getName() + " (" + e.getExportedNode().getNodeClassName() + "):\n" ;
        ret += "            ";
        for (String p : e.getProperties()) {
            ret += e.getExportedNode().getName() + "." + p + ", ";
        }
        ret = ret.substring(0, ret.length()-2) + "\n";
        return ret;
    }

    public static String printConnections(ChainNode node) {
        String ret = "";
        if (node.getInputs().size()>0) {
            ret += (" - Inputs:\n");
            String inputs = "";
            for (ChainNode in : node.getInputs())
                inputs += in.getName() + " (" + in.getNodeClassName() + "), ";
            ret += ("      " + inputs.substring(0, inputs.length()-2) + "\n");
        } else ret += (" - Inputs: none.\n");

        if (node.getOutputs().size()>0) {
            String outputs = "";
            for (ChainNode mout : node.getOutputs())
                outputs += mout.getName() + " (" + mout.getNodeClassName() + "), ";
            ret += (" - Outputs:\n");
            ret += ("      " + outputs.substring(0, outputs.length()-2) + "\n");
        } else ret += (" - Outputs: none.\n");
        return ret;
    }

    public static String printModules(Chain chain) {
        String ret = "";
        for (ChainNode n : chain.getChildNodes().values()) {
            if (n instanceof Module)
                ret += n.getName() + " (" + ((Module)n).getNodeClassName() + ")" + ", ";
        }
        if (ret.length()>0) ret = ret.substring(0, ret.length()-2);
        return ret;
    }

    public static String printProperty(ChainNode node, PropertyInfo prop, Object val) {
        if (prop.isRepeatable()) {
            List repVal = (ArrayList)val;
            String ret = "";
            if (repVal==null) return "<unknown> (" + prop.getName() + ");";
            else {
                for (Object v : repVal) {
                    ret += getOneProperty(node, prop, v) + "; ";
                }
                if (ret.length()<1) return ret;
                else return ret.substring(0, ret.length()-1);
            }
        } else return getOneProperty(node, prop, val);
    }

    private static String getOneProperty(ChainNode node, PropertyInfo prop, Object val) {
        PropertyParser p = prop.getParser();
        String sval;

        sval = p.propToString(val, node);
        if (p instanceof KeyValueParser || (prop.getAttributes().containsKey("open-sequence") && prop.getAttributes().get("open-sequence").equals("true"))) {
            return sval;
        } else return prop.getName() + ": " + sval;
    }

    public static String printModuleAbout(Module module) {
        NodeInfo moduleInfo = module.getNodeInfo();
        String ret = "\n==" + moduleInfo.getName() + "======\n";
        if (moduleInfo.getDescription()!=null && moduleInfo.getDescription().length()>0)
            ret += moduleInfo.getDescription() + "\n\n";
        if (moduleInfo.getUsage()!=null && moduleInfo.getUsage().length()>0)
                ret += "Usage: " + moduleInfo.getUsage() + "\n";
        ret += "Syntax:\n";
        ret += "   " + moduleInfo.getSyntax() + "\n\n";
        ret += "Properties:\n";
        for (PropertyInfo arg : moduleInfo.getProperties()) {
            String argHelp = "";
            if (arg.isOrdered()) argHelp = "(" + arg.getOrderIndex() + ") ";
            else argHelp = "(.) ";
            argHelp += arg.getName() + " (" + arg.getParser().getPropertyType() + "): " + arg.getDescription();

            String attributes = "";
            if (arg.isRepeatable()) attributes += "repeatable, ";
            if (arg.isOptional()) attributes += "optional (default: " + arg.getDefaultValueString() + "), ";
            if (module.isPropertySettable(arg.getName()))
                attributes += "settable, ";
            if (module.isPropertyGettable(arg.getName()))
                attributes += "gettable, ";
            if (attributes.length()>0) argHelp += " (" + attributes.substring(0,attributes.length()-2) + ").";
            ret += argHelp + "\n";
        }

        
        Method[] commands = module.getModuleCommands();

        if (commands.length>0) {
            ret += "\nCommands:\n";
            String sCommands = "";
            for (Method command : commands) {
                String params = "";
                for (Class param : command.getParameterTypes()){
                    DataType t = DataType.findType(param);
                    String st;
                    if (t==null) st = "unknown";
                    else st = t.getName();
                    params += st + ", ";
                }
                if (command.getParameterTypes().length>0) params = params.substring(0, params.length()-2);
                sCommands += " - " + command.getName() + "{" + params + "}" + "\n ";
            }
            ret += sCommands + "\n";
        }
        return ret;
    }

    public static String printVarTable(VarTable table) {
        String out = "";
        for (String name : table.keySet()) {
            Variable v = table.get(name);
            out += ("   - " + (table.isPublicVar(name)?"public ":"") + table.get(name) + "\n");
        }
        if (out.trim().length()==0) return "   - No variables defined.";
        else return out;
    }

    public static String printAvailableModules(String pack) {
        String ret = "";
        TreeMap<String, ModuleInfo> modules = PluginLoader.getAvailableModules(pack);
        for (String name : modules.keySet()) {
            ModuleInfo modInfo = modules.get(name);
            String description = modInfo.getDescription();
            ret += ("   - " + name + ": " + description + "\n");
        }
        return ret;
    }
}
